package com.sailing.xjpb.ipaas.client.core;

import com.sailing.xjpb.common.SailingConstants;
import com.sailing.xjpb.common.exception.IpaasClientException;
import com.sailing.xjpb.common.web.ResponseCode;
import com.sailing.xjpb.ipaas.client.common.IpaasClientConstants;
import com.sailing.xjpb.ipaas.client.config.IpaasClientProperties;
import com.sailing.xjpb.ipaas.client.entity.dto.IpaasPageDTO;
import com.sailing.xjpb.ipaas.client.entity.dto.SipResultDTO;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.*;
import org.springframework.web.client.ResourceAccessException;
import org.springframework.web.client.RestTemplate;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

/**
 * ipaas 客户端请求工具类
 *
 * @author YaoWei
 * createDate 2021/12/14
 */
@Getter
@Slf4j
public class IpaasHttpClient {

    private static final String IPAAS_ERROR_MSG = "ipaas接口请求异常！";

    private final RestTemplate restTemplate;

    /**
     * ipaas 认证地址
     */
    private final String ipaasOauthUrl;

    /**
     * ipaas配置信息
     */
    private final IpaasClientProperties ipaasClientProperties;

    public IpaasHttpClient(RestTemplate restTemplate, IpaasClientProperties ipaasClientProperties) {
        this.restTemplate = restTemplate;
        this.ipaasOauthUrl = ipaasClientProperties.getOauthUrl();
        this.ipaasClientProperties = ipaasClientProperties;
    }

    public <T> T exchange(String url, HttpMethod method, Class<T> dtoType) {
        return exchange(url, method, dtoType, new HashMap<>(1));
    }

    public <T> T exchange(String url, HttpMethod method, Class<T> dtoType, Map<String, Object> uriVariables) {
        return exchange(url, method, null, dtoType, uriVariables, null);
    }

    public <T> T exchange(String url, HttpMethod method, String authorization, Class<T> dtoType) {
        HttpEntity<?> httpEntity = getRequestEntity(authorization, null);
        return exchange(url, method, httpEntity, dtoType, new HashMap<>(1));
    }

    public <T> T exchange(String url, HttpMethod method, String authorization, Class<T> dtoType,
                          Map<String, Object> uriVariables, Object body) {
        HttpEntity<?> httpEntity = getRequestEntity(authorization, body);
        return exchange(url, method, httpEntity, dtoType, uriVariables);
    }

    public <T> T exchange(String url, HttpMethod method, Class<T> dtoType, Map<String, Object> uriVariables,
                          Object body) {
        HttpEntity<?> httpEntity = getRequestEntity(null, body);
        return exchange(url, method, httpEntity, dtoType, uriVariables);
    }

    public <T> T exchange(String url, HttpMethod method, Class<T> dtoType, Object body) {
        HttpEntity<?> httpEntity = getRequestEntity(null, body);
        return exchange(url, method, httpEntity, dtoType, new HashMap<>(1));
    }

    public <T> T exchange(String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> dtoType, Map<String,
            Object> uriVariables) {
        try {
            String fullUrl = getFullUrl(url);
            ResponseEntity<T> responseEntity = restTemplate.exchange(fullUrl, method, requestEntity, dtoType,
                    uriVariables);
            return responseEntity.getBody();
        } catch (ResourceAccessException e) {
            throw new IpaasClientException(ResponseCode.T_4000, IPAAS_ERROR_MSG, e);
        }
    }

    public <T> List<T> list(String url, HttpMethod method, String authorization, Class<T> dtoType) {
        return list(url, method, authorization, dtoType, new HashMap<>(1), null);
    }

    public <T> List<T> list(String url, HttpMethod method, Class<T> dtoType) {
        return list(url, method, null, dtoType, new HashMap<>(1), null);
    }

    public <T> List<T> list(String url, HttpMethod method, Class<T> dtoType, Map<String, Object> uriVariables) {
        return list(url, method, null, dtoType, uriVariables, null);
    }

    public <T> List<T> listV1(String url, HttpMethod method, Class<T> dtoType, Map<String, Object> uriVariables) {
        return listV1(url, method, null, dtoType, uriVariables, null);
    }

    public <T> List<T> list(String url, HttpMethod method, Class<T> dtoType, Map<String, Object> uriVariables,
                            Object body) {
        return list(url, method, null, dtoType, uriVariables, body);
    }

    public <T> List<T> list(String url, HttpMethod method, Class<T> dtoType, Object body) {
        return list(url, method, null, dtoType, new HashMap<>(1), body);
    }

    public <T> List<T> list(String url, HttpMethod method, String authorization, Class<T> dtoType, Map<String,
            Object> uriVariables, Object body) {
        HttpEntity<?> httpEntity = getRequestEntity(authorization, body);
        return list(url, method, httpEntity, dtoType, uriVariables);
    }

    public <T> List<T> listV1(String url, HttpMethod method, String authorization, Class<T> clazz, Map<String,
            Object> uriVariables, Object body) {
        HttpEntity<?> httpEntity = getRequestEntity(authorization, body);
        return listV1(url, method, httpEntity, clazz, uriVariables);
    }


    public <T> List<T> list(String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> clazz, Map<String,
            Object> uriVariables) {
        try {
            String fullUrl = getFullUrl(url);
            Type dtoType = getListType(clazz);
            ResponseEntity<List<T>> responseEntity = restTemplate.exchange(fullUrl, method, requestEntity,
                    ParameterizedTypeReference.forType(dtoType), uriVariables);
            return responseEntity.getBody();
        } catch (ResourceAccessException e) {
            throw new IpaasClientException(ResponseCode.T_4000, IPAAS_ERROR_MSG, e);
        }

    }

    /**
     * 适配ipaas新版返回值接口
     * 返回体结构：
     * {
     * "data": [...],
     * "errorMessage": null,
     * "message": "success",
     * "status": "0"
     * }
     *
     * @param url           地址
     * @param method        HTTP 请求方式
     * @param requestEntity 请求头
     * @param clazz         返回体类型
     * @param uriVariables  请求参数
     * @param <T>           返回体中data的类型
     * @return 返回信息
     */
    public <T> List<T> listV1(String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> clazz, Map<String,
            Object> uriVariables) {
        try {
            String fullUrl = getFullUrl(url);
            Type listType = getListType(clazz);
            Type dtoType = getSipResultType(listType);
            ResponseEntity<SipResultDTO<List<T>>> responseEntity = restTemplate.exchange(fullUrl, method,
                    requestEntity, ParameterizedTypeReference.forType(dtoType), uriVariables);
            SipResultDTO<List<T>> result = responseEntity.getBody();
            return this.getResponseResult(fullUrl, result);
        } catch (ResourceAccessException e) {
            throw new IpaasClientException(ResponseCode.T_4000, IPAAS_ERROR_MSG, e);
        }
    }

    /**
     * 适配ipaas新版返回值接口
     * 返回体结构：
     * {
     * "data": [...],
     * "errorMessage": null,
     * "message": "success",
     * "status": "0"
     * }
     *
     * @param url           地址
     * @param method        HTTP 请求方式
     * @param requestEntity 请求头
     * @param clazz         返回体类型
     * @param uriVariables  请求参数
     * @param <T>           返回体中data的类型
     * @return 返回信息
     */
    public <T> T exchangeV1(String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> clazz, Map<String,
            Object> uriVariables) {
        try {
            String fullUrl = getFullUrl(url);
            Type dtoType = getSipResultType(clazz);
            ResponseEntity<SipResultDTO<T>> responseEntity = restTemplate.exchange(fullUrl, method,
                    requestEntity, ParameterizedTypeReference.forType(dtoType), uriVariables);
            SipResultDTO<T> result = responseEntity.getBody();
            return getResponseResult(fullUrl, result);
        } catch (ResourceAccessException e) {
            throw new IpaasClientException(ResponseCode.T_4000, IPAAS_ERROR_MSG, e);
        }
    }

    /**
     * 适配ipaas新版返回值接口
     * 返回体结构：
     * {
     * "data": [...],
     * "errorMessage": null,
     * "message": "success",
     * "status": "0"
     * }
     *
     * @param url          地址
     * @param method       HTTP 请求方式
     * @param clazz        返回体类型
     * @param uriVariables 请求参数
     * @param body         请求体
     * @param <T>          返回体中data的类型
     * @return 返回信息
     */
    public <T> IpaasPageDTO<T> page(String url, HttpMethod method, Class<T> clazz, Map<String,
            Object> uriVariables, Object body) {
        HttpEntity<?> httpEntity = getRequestEntity(null, body);
        return page(url, method, httpEntity, clazz, uriVariables);
    }

    /**
     * 适配ipaas新版返回值接口
     * 返回体结构：
     * {
     * "data": [...],
     * "errorMessage": null,
     * "message": "success",
     * "status": "0"
     * }
     *
     * @param url           地址
     * @param method        HTTP 请求方式
     * @param requestEntity 请求头
     * @param clazz         返回体类型
     * @param uriVariables  请求参数
     * @param <T>           返回体中data的类型
     * @return 返回信息
     */
    public <T> IpaasPageDTO<T> page(String url, HttpMethod method, HttpEntity<?> requestEntity, Class<T> clazz,
                                    Map<String,
                                            Object> uriVariables) {
        try {
            String fullUrl = getFullUrl(url);
            Type pageType = getPageType(clazz);
            Type dtoType = getSipResultType(pageType);
            ResponseEntity<SipResultDTO<IpaasPageDTO<T>>> responseEntity = restTemplate.exchange(fullUrl, method,
                    requestEntity, ParameterizedTypeReference.forType(dtoType), uriVariables);
            SipResultDTO<IpaasPageDTO<T>> result = responseEntity.getBody();
            return getResponseResult(fullUrl, result);
        } catch (ResourceAccessException e) {
            throw new IpaasClientException(ResponseCode.T_4000, IPAAS_ERROR_MSG, e);
        }
    }

    private <T> Type getListType(Class<T> clazz) {
        return new ParameterizedType() {
            public Type getRawType() {
                return List.class;
            }

            public Type[] getActualTypeArguments() {
                return new Type[]{clazz};
            }

            public Type getOwnerType() {
                return null;
            }
        };
    }

    private Type getSipResultType(Type clazz) {
        return new ParameterizedType() {
            public Type getRawType() {
                return SipResultDTO.class;
            }

            public Type[] getActualTypeArguments() {
                return new Type[]{clazz};
            }

            public Type getOwnerType() {
                return null;
            }
        };
    }

    private <T> Type getPageType(Class<T> clazz) {
        return new ParameterizedType() {
            public Type getRawType() {
                return IpaasPageDTO.class;
            }

            public Type[] getActualTypeArguments() {
                return new Type[]{clazz};
            }

            public Type getOwnerType() {
                return null;
            }
        };
    }


    private HttpEntity<?> getRequestEntity(String authorization, Object body) {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        if (authorization != null) {
            UserContext.setAuthorization(authorization);
        }
        String token = UserContext.getBearerToken();
        if (token != null) {
            headers.put(IpaasClientConstants.AUTHORIZATION, Collections.singletonList(token));
        }
        // add since V2.1 适配明镜，动态获取租户ID
        String tenantCode = UserContext.getUserTenantCode() != null ?
                UserContext.getUserTenantCode() : ipaasClientProperties.getTenantCode();
        if (tenantCode != null) {
            headers.put(IpaasClientConstants.HEADER_TENANT_CODE, Collections.singletonList(tenantCode));
        }
        // add by YaoWei 解决ipaas获取容器IP而非真实ip问题
        String ipAddress = UserContext.getIpAddress();
        if (StringUtils.isNotEmpty(ipAddress)) {
            headers.put(IpaasClientConstants.X_FORWARDED_FOR, Collections.singletonList(ipAddress));
        }
        return body == null ? new HttpEntity<>(headers) : new HttpEntity<>(body, headers);
    }

    public String getFullUrl(String uri) {
        return getIpaasOauthUrl() + ipaasClientProperties.getVersion().getRequestUrI(uri);
    }

    /**
     * ipaas响应检查-异常直接报错
     *
     * @param fullUrl 请求地址
     * @param result  返回结果
     * @param <T>     返回值类型
     * @return 返回值
     */
    private <T> T getResponseResult(String fullUrl, SipResultDTO<T> result) {
        if (Objects.isNull(result)) {
            throw new IpaasClientException(ResponseCode.T_4000, "ipaas接口请求异常：");
        }
        if (!SailingConstants.BOOLEAN_FALSE.equals(result.getStatus())) {
            log.error("ipaas接口请求异常：url={}，返回值={}", fullUrl, result);
            throw new IpaasClientException(ResponseCode.T_4000, "ipaas接口请求异常：" + result.getErrorMessage());
        }
        return result.getData();
    }

}
